/*************************************************************************** 
 * (c) 2006 Dmitry Osinovsky, osdmsoftware@gmail.com
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU Lesser General Public License as        *
 *   published by the Free Software Foundation; either version 2 of the    *
 *   License, or (at your option) any later version.                       *
 *                                                                         *
 ***************************************************************************/

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using Microsoft.Win32;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using OsdmSoftware.CommonLibrary;

namespace OsdmSoftware.PocketPicture
{
    public delegate void BitmapMouseDownHandler(int x, int y);
    public partial class BitmapZoomer: Control, IScrollableControl
    {
        List<Point> selectedPoints = new List<Point>();
        Rectangle selectionRect = Rectangle.Empty;
        Bitmap bitmap;
        int zoomFactor;
        public Label debugLabel;
        int showGridOnZoomGreaterThan;
        int xScroll;
        int yScroll;
        int xMaxScroll;
        int yMaxScroll;
        int xSmallChange;
        int ySmallChange;
        int xLargeChange;
        int yLargeChange;
        bool mouseDownWasOnOurControl = false;

        const int SelectionRectThickness = 3;
        const int SelectionRectOffset = 2;
        public const int ZoomFactorUnit = 100;

        public event EventHandler ZoomFactorChanged;
        public event BitmapMouseDownHandler BitmapMouseDown;
        public event BitmapMouseDownHandler BitmapMouseMove;
        public event BitmapMouseDownHandler BitmapMouseUp;

        public List<Point> SelectedPoints { get { return selectedPoints; } }
        public Rectangle SelectionRect { get { return selectionRect; } set { selectionRect = value; } }
        public Bitmap Bitmap
        {
            get { return bitmap; }
            set { bitmap = value; ChangeSize(); }
        }

        public int ZoomFactor
        {
            get
            {
                return zoomFactor;
            }
            set
            {
                if (zoomFactor != value)
                {
                    if (zoomFactor != 0 && bitmap != null && value != 0)
                    {
                        // Will be zooming by center point
                        int cornerx = XScroll * ZoomFactorUnit / zoomFactor;
                        int cornery = YScroll * ZoomFactorUnit / zoomFactor;
                        int centerx;
                        if (bitmap.Width - cornerx < Width * ZoomFactorUnit / zoomFactor)
                            centerx = cornerx + (bitmap.Width - cornerx) / 2;
                        else
                            centerx = cornerx + (Width * ZoomFactorUnit / zoomFactor) / 2;
                        int centery;
                        if (bitmap.Height - cornerx < Height * ZoomFactorUnit / zoomFactor)
                            centery = cornery + (bitmap.Height - cornery) / 2;
                        else
                            centery = cornery + (Height * ZoomFactorUnit / zoomFactor) / 2;

                        SetZoom(value, centerx, centery);
                    }
                    else
                        SetZoom(value, -1, -1);
                }
            }
        }
        public int ShowGridOnZoomGreaterThan { 
            get { return showGridOnZoomGreaterThan; }
            set { showGridOnZoomGreaterThan = value; Refresh(); }
        }
        public int XScroll { get { return xScroll; } set { xScroll = value; Invalidate(); } }
        public int YScroll { get { return yScroll; } set { yScroll = value; Invalidate(); } }
        public int XMaxScroll { get { return xMaxScroll; } set { xMaxScroll = value; } }
        public int YMaxScroll { get { return yMaxScroll; } set { yMaxScroll = value; } }
        public int XSmallChange { get { return xSmallChange; } set { xSmallChange = value; } }
        public int YSmallChange { get { return ySmallChange; } set { ySmallChange = value; } }
        public int XLargeChange { get { return xLargeChange; } set { xLargeChange = value; } }
        public int YLargeChange { get { return yLargeChange; } set { yLargeChange = value; } }

        public Control Control { get { return this; } }

        public BitmapZoomer()
        {
            InitializeComponent();
        }

        void ChangeSize()
        {
            if (bitmap != null)
            {
                xMaxScroll = bitmap.Width * zoomFactor / ZoomFactorUnit + 1;
                yMaxScroll = bitmap.Height * zoomFactor / ZoomFactorUnit + 1;
                xSmallChange = Math.Max(zoomFactor / ZoomFactorUnit, 1);
                ySmallChange = Math.Max(zoomFactor / ZoomFactorUnit, 1);
                xLargeChange = Width / 2;
                yLargeChange = Height / 2;
                ((ScrollBox) Parent).UpdateScrollBars();
            }
            Refresh();
        }

        public void SetZoom(int newZoomFactor, int centerX, int centerY)
        {
            if (centerX != -1)
            {
                int cornerx = Math.Max(0, centerX - (Width * ZoomFactorUnit / newZoomFactor) / 2);
                int cornery = Math.Max(0, centerY - (Height * ZoomFactorUnit / newZoomFactor) / 2);
                xScroll = cornerx * newZoomFactor / ZoomFactorUnit;
                yScroll = cornery * newZoomFactor / ZoomFactorUnit;
            }

            zoomFactor = newZoomFactor;
            ChangeSize();
            if (ZoomFactorChanged != null)
                ZoomFactorChanged(this, EventArgs.Empty);
        }


        public void AddSelectedPoint(int x, int y)
        {
            selectedPoints.Add(new Point(x, y));
            Graphics gr = this.CreateGraphics();
            DrawSelectedPoint(x, y, gr, 0, xScroll, yScroll);
            gr.Dispose();
        }

        /*void DrawAt(int x, int y, Graphics gr, int grid, int xoffset, int yoffset)
        {
            if (grid == 0)
                InvalidateBitmap(x - 1, y - 1, 3, 3);
            else
                gr.FillRectangle(new SolidBrush(bitmap.GetPixel(x, y)),
                    x * zoomFactor / ZoomFactorUnit + grid - xoffset, y * zoomFactor / ZoomFactorUnit + grid - yoffset,
                    zoomFactor / ZoomFactorUnit - grid, zoomFactor / ZoomFactorUnit - grid);
        }*/

        Color GetContrastColor(Color c)
        {
            if (c.R < 128)
                return Color.FromArgb(255, 0, 0);

            if (c.G < 128)
                return Color.FromArgb(0, 255, 0);

            if (c.B < 128)
                return Color.FromArgb(0, 0, 255);

            return Color.FromArgb(0, 0, 0);

        }

        void DrawSelectedPoint(int x, int y, Graphics gr, int grid, int xoffset, int yoffset)
        {
            Color c = GetContrastColor(bitmap.GetPixel(x, y));
            Pen p = new Pen(c, 2);
            int size = 3;
            if (zoomFactor > 5 * ZoomFactorUnit )
                size = zoomFactor / ZoomFactorUnit / 2;
            gr.DrawLine(p,
                x * zoomFactor / ZoomFactorUnit + zoomFactor / ZoomFactorUnit / 2 - size - xoffset,
                y * zoomFactor / ZoomFactorUnit + zoomFactor / ZoomFactorUnit / 2 - size - yoffset,
                x * zoomFactor / ZoomFactorUnit + zoomFactor / ZoomFactorUnit / 2 + size - xoffset,
                y * zoomFactor / ZoomFactorUnit + zoomFactor / ZoomFactorUnit / 2 + size - yoffset);
            gr.DrawLine(p,
                x * zoomFactor / ZoomFactorUnit + zoomFactor / ZoomFactorUnit / 2 - size - xoffset,
                y * zoomFactor / ZoomFactorUnit + zoomFactor / ZoomFactorUnit / 2 + size - yoffset,
                x * zoomFactor / ZoomFactorUnit + zoomFactor / ZoomFactorUnit / 2 + size - xoffset,
                y * zoomFactor / ZoomFactorUnit + zoomFactor / ZoomFactorUnit / 2 - size - yoffset);
        }

        protected virtual void OnBitmapMouseDown(int x, int y)
        {
            if (BitmapMouseDown != null)
                BitmapMouseDown(x, y);
        }

        protected virtual void OnBitmapMouseMove(int x, int y)
        {
            if (BitmapMouseMove != null)
                BitmapMouseMove(x, y);
        }

        protected virtual void OnBitmapMouseUp(int x, int y)
        {
            if (BitmapMouseUp != null)
                BitmapMouseUp(x, y);
        }

        public void ForgetMouseDown()
        {
            mouseDownWasOnOurControl = false;
        }

        protected override void OnMouseDown(MouseEventArgs e)
        {
            if (mouseDownWasOnOurControl)
            {
                OnMouseMove(e);
            }
            else if (bitmap != null)
            {
                int rx = (e.X + xScroll) * ZoomFactorUnit / zoomFactor;
                int ry = (e.Y + yScroll) * ZoomFactorUnit / zoomFactor;
                if (rx >= 0 && rx < bitmap.Width && ry >= 0 && ry < bitmap.Height)
                {
                    Capture = true;
                    mouseDownWasOnOurControl = true;
                    OnBitmapMouseDown(rx, ry);
                }
            }

            base.OnMouseDown(e);
        }

        protected override void OnMouseMove(MouseEventArgs e)
        {
            if (bitmap != null && mouseDownWasOnOurControl)
            {
                int rx = (e.X + xScroll) * ZoomFactorUnit / zoomFactor;
                int ry = (e.Y + yScroll) * ZoomFactorUnit / zoomFactor;
                if (rx >= 0 && rx < bitmap.Width && ry >= 0 && ry < bitmap.Height)
                    OnBitmapMouseMove(rx, ry);
            }
            base.OnMouseMove(e);
        }

        protected override void OnMouseUp(MouseEventArgs e)
        {
            if (mouseDownWasOnOurControl)
            {
                Capture = false;
                mouseDownWasOnOurControl = false;
                int rx = (e.X + xScroll) * ZoomFactorUnit / zoomFactor;
                int ry = (e.Y + yScroll) * ZoomFactorUnit / zoomFactor;
                OnBitmapMouseUp(rx, ry);
            }
            base.OnMouseUp(e);
        }

        protected override void OnPaintBackground(PaintEventArgs e)
        {
            //base.OnPaintBackground(e);
        }

        public void InvalidateBitmap(int x, int y, int w, int h)
        {
            InvalidateBitmap(x, y, w, h, false);
        }

        public void InvalidateBitmap(int x, int y, int w, int h, bool updateSelection)
        {
            Graphics gr = this.CreateGraphics();
            if (x < 0)
                x = 0;
            if (y < 0)
                y = 0;
            if (x + w > bitmap.Width)
                w = bitmap.Width - x;

            if (y + h > bitmap.Height)
                h = bitmap.Height - y;

            int offset = 0;
            if (updateSelection)
                offset = SelectionRectOffset + SelectionRectThickness + 1;
            if (zoomFactor < ZoomFactorUnit)
                offset++;
            DoPaint(gr, new Rectangle(x * zoomFactor / ZoomFactorUnit - xScroll - offset,
                y * zoomFactor / ZoomFactorUnit - yScroll - offset,
                w * zoomFactor / ZoomFactorUnit + 2 * offset,
                h * zoomFactor / ZoomFactorUnit + 2 * offset));
            gr.Dispose();
            //Invalidate(new Rectangle(x * zoomFactor, y * zoomFactor, w * zoomFactor, h * zoomFactor));
        }

        void DoPaint(Graphics graphics, Rectangle clipRectangle)
        {
            if (clipRectangle.Width <= 0 || clipRectangle.Height <= 0)
                return;

            int xoffset = 0, yoffset = 0;
            if (debugLabel != null)
            {
                debugLabel.Text = clipRectangle.X.ToString() + ":" +
                    clipRectangle.Y.ToString() + " - " +
                    clipRectangle.Width.ToString() + ":" +
                    clipRectangle.Height.ToString();
            }

            Bitmap bmp = null;
            try
            {
                bmp = new Bitmap(clipRectangle.Width, clipRectangle.Height, PixelFormat.Format32bppRgb);
                xoffset = clipRectangle.X + xScroll;
                yoffset = clipRectangle.Y + yScroll;
            }
            catch
            {
                graphics.FillRectangle(new SolidBrush(Color.Red), clipRectangle);
                graphics.DrawString("Out of memory", new Font("Arial", 8, FontStyle.Regular), new SolidBrush(Color.Black), 0, 0);
                return;
            }

            Graphics gr = Graphics.FromImage(bmp);
            gr.FillRectangle(new SolidBrush(BackColor), 0, 0, clipRectangle.Width, clipRectangle.Height);
            int xstart;
            int ystart;
            int xend;
            int yend;
            int w, h;

            if (bitmap != null && zoomFactor > 0)
            {
                int add = 1;
                if (zoomFactor < ZoomFactorUnit)
                    add += ZoomFactorUnit / zoomFactor;
                xstart = (clipRectangle.Left + xScroll) * ZoomFactorUnit / zoomFactor;
                ystart = (clipRectangle.Top + yScroll) * ZoomFactorUnit / zoomFactor;
                xend = (clipRectangle.Right + xScroll) * ZoomFactorUnit / zoomFactor + add;
                yend = (clipRectangle.Bottom + yScroll) * ZoomFactorUnit / zoomFactor + add;

                if (xstart < 0)
                    xstart = 0;
                if (ystart < 0)
                    ystart = 0;
                if (xend >= bitmap.Width)
                    xend = bitmap.Width - 1;
                if (yend >= bitmap.Height)
                    yend = bitmap.Height - 1;

                w = xend - xstart + 1;
                h = yend - ystart + 1;

                if (w > 0 && h > 0)
                {
                    int grid;
                    if (zoomFactor > showGridOnZoomGreaterThan * ZoomFactorUnit )
                        grid = 1;
                    else
                        grid = 0;

                    Pen pen = new Pen(this.ForeColor);
                    if (grid == 0)//(zoomFactor == ZoomFactorUnit )
                    {
                        // Do you know that Graphics.DrawImage will take negative source/target width/height
                        // as an argument and would not complain. Not a single bit.
                        // Except that in some peculiar cases afterwards you will have random access violations
                        // in completely unrelated GDI calls. Best wishes to guys in Redmond.

                        gr.DrawImage(bitmap, new Rectangle(
                            xstart * zoomFactor / ZoomFactorUnit - xoffset,
                            ystart * zoomFactor / ZoomFactorUnit - yoffset,
                            w * zoomFactor / ZoomFactorUnit,
                            h * zoomFactor / ZoomFactorUnit),
                            new Rectangle(xstart, ystart, w, h), GraphicsUnit.Pixel);
                        gr.DrawLine(pen,
                            bitmap.Width * zoomFactor / ZoomFactorUnit - xoffset,
                            -1 - yoffset,
                            bitmap.Width * zoomFactor / ZoomFactorUnit - xoffset,
                            bitmap.Height * zoomFactor / ZoomFactorUnit - yoffset);
                        gr.DrawLine(pen,
                            -1 - xoffset,
                            bitmap.Height * zoomFactor / ZoomFactorUnit - yoffset,
                            bitmap.Width * zoomFactor / ZoomFactorUnit - xoffset,
                            bitmap.Height * zoomFactor / ZoomFactorUnit - yoffset);
                        gr.DrawLine(pen,
                            bitmap.Width * zoomFactor / ZoomFactorUnit - xoffset,
                            -1 - yoffset,
                            -1 - xoffset,
                            -1 - yoffset);
                        gr.DrawLine(pen, 
                            -1 - xoffset,
                            bitmap.Height * zoomFactor / ZoomFactorUnit - yoffset,
                            -1 - xoffset,
                            -1 - yoffset);
                    }
                    else
                    unsafe
                    {
                        Rectangle rect1 = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
                        BitmapData bmpData = bitmap.LockBits(rect1, ImageLockMode.ReadWrite, ColorBgra.PixelFormat);
                        byte* pTargetRow = (byte*) bmpData.Scan0.ToPointer();
                        pTargetRow += bmpData.Stride * ystart;
                        for (int y = ystart; y <= yend; y++, pTargetRow += bmpData.Stride)
                        {
                            ColorBgra* pTargetPixel = (ColorBgra*) pTargetRow;
                            pTargetPixel += xstart;

                            for (int x = xstart; x <= xend; x++, pTargetPixel++)
                            {
                                if (grid == 0)
                                    InvalidateBitmap(x - 1, y - 1, 3, 3);
                                else
                                    gr.FillRectangle(new SolidBrush(pTargetPixel->ToColor()),
                                        x * zoomFactor / ZoomFactorUnit + grid - xoffset,
                                        y * zoomFactor / ZoomFactorUnit + grid - yoffset,
                                        zoomFactor / ZoomFactorUnit - grid, 
                                        zoomFactor / ZoomFactorUnit - grid);
                            }
                        }

                        bitmap.UnlockBits(bmpData);

                        for (int x = xstart; x <= xend + 1; x++)
                        {
                            gr.DrawLine(pen,
                                x * zoomFactor / ZoomFactorUnit - xoffset,
                                ystart * zoomFactor / ZoomFactorUnit - yoffset,
                                x * zoomFactor / ZoomFactorUnit - xoffset,
                                (yend + 1) * zoomFactor / ZoomFactorUnit - yoffset);
                        }

                        for (int y = ystart; y <= yend + 1; y++)
                        {
                            gr.DrawLine(pen,
                                xstart * zoomFactor / ZoomFactorUnit - xoffset,
                                y * zoomFactor / ZoomFactorUnit - yoffset,
                                (xend + 1) * zoomFactor / ZoomFactorUnit - xoffset,
                                y * zoomFactor / ZoomFactorUnit - yoffset);
                        }
                    }
                }

                if (selectionRect.Width > 0 && selectionRect.Height > 0)
                {
                    Pen selpen = new Pen(Color.Gray, SelectionRectThickness);
                    selpen.DashStyle = DashStyle.Dash;
                    gr.DrawRectangle(selpen, selectionRect.X * 
                        zoomFactor / ZoomFactorUnit - SelectionRectOffset - xoffset, 
                        selectionRect.Y * 
                        zoomFactor / ZoomFactorUnit - SelectionRectOffset - yoffset,
                        selectionRect.Width * 
                        zoomFactor / ZoomFactorUnit + SelectionRectOffset * 2,
                        selectionRect.Height * 
                        zoomFactor / ZoomFactorUnit + SelectionRectOffset * 2);
                }

                foreach (Point p in selectedPoints)
                {
                    DrawSelectedPoint(p.X, p.Y, gr, 0, xoffset, yoffset);
                }

            }

            graphics.DrawImage(bmp, clipRectangle, new Rectangle(0, 0, clipRectangle.Width, clipRectangle.Height), GraphicsUnit.Pixel);

            try
            {
                gr.Dispose();
                gr = null;
            }
            catch
            {
                throw;
            }

            try
            {
                bmp.Dispose();
                bmp = null;
            }
            catch
            {
                throw;
            }
        }

        protected override void OnPaint(PaintEventArgs pe)
        {
            DoPaint(pe.Graphics, pe.ClipRectangle);
            base.OnPaint(pe);
        }
    }
}
